﻿module Perceptron

open System
open System.IO
open System.Collections.Generic
open MathNet.Numerics
open MathNet.Numerics.FSharp
open MathNet.Numerics.LinearAlgebra.Double

//set point as default decimal separator
System.Threading.Thread.CurrentThread.CurrentCulture <- System.Globalization.CultureInfo.CreateSpecificCulture("En")
System.Threading.Thread.CurrentThread.CurrentUICulture <- System.Globalization.CultureInfo.CreateSpecificCulture("En")

type PerceptronData =
  {
    X : DenseVector
    Y : float
    mutable Missclassification : int
  }

Console.WriteLine("Insert the maximum number of missclassification allowed for each entry before it is ignored (negtive number
  means no bound is used)")
let bound = Console.ReadLine() |> Convert.ToInt32
Console.WriteLine("Insert the directory for the output file (if left blank then the output file will be
  created in the program directory)")
let mutable path = Console.ReadLine()
if path.EndsWith("\\") |> not then
  path <- path + "\\"
Directory.CreateDirectory(path) |> ignore
Console.WriteLine("Insert the iris variety to classify")
let perception_type = Console.ReadLine() //type of classification
let use_bound = bound > 0
let test = File.ReadAllText("iris.data")
let dimensions = 4 //dimensions of the X vector
let dataList = test.Split(',','\n') |> Seq.toList |> List.filter(fun s -> s <> "")
let perceptron_vector = ResizeArray()

//build a redcord made of a vector of 4 float components
let rec gather_data data counter x_vector =
  if data = [] then
    ()
  else
    if counter = dimensions then
      let data_record =
        {
          X = DenseVector(x_vector @ [1.0] |> List.toArray) //vector containing the training set input data
          Y = if data.Head = perception_type then 1.0 else -1.0 //vector containing the expected output
          Missclassification = 0 //missclassification counter
        }
      perceptron_vector.Add(data_record)
      gather_data data.Tail 0 []
    else
      gather_data data.Tail (counter + 1) (x_vector @ [data.Head |> Double.Parse])

gather_data dataList 0 []

//DEBUG
//let print_perceptron_data =
//  Console.WriteLine("PERCEPTRON DATA: \n\n")
//  for (i,e) in perceptron_vector |> Seq.mapi(fun i e -> (i,e)) do
//    Console.WriteLine("X vector = " + e.X.ToString() + 
//      (sprintf " Y = %f Miss = %d Index = % d" e.Y e.Missclassification (i + 1)))
//  Console.WriteLine("")
//
////DEBUG
//do print_perceptron_data

let mutable w_vector = DenseVector([|0.0 ; 0.0 ; 0.0 ; 0.0; 0.0|])
let incr_rate = 0.5 //0.5
let mutable classified = false

while not classified do
  classified <- true
  for v in perceptron_vector do
    let margin = v.Y * w_vector * v.X
    if (margin <= 0.0 && (v.Missclassification < bound || (not use_bound))) then
      v.Missclassification <- v.Missclassification + 1
      w_vector <- w_vector + incr_rate * v.Y * v.X
      classified <- false

Console.WriteLine("W vector = [" + w_vector.ToString() + "]")
//write the output vector to the proper file
Console.WriteLine("Writing output to file...")
match perception_type with
| "Iris-setosa" -> path <- path + "setosa.txt"
| "Iris-versicolor" -> path <- path + "versicolor.txt"
| "Iris-virginica" -> path <- path + "virginica.txt"
| _ -> path <- path + "unknown.txt"


let output_text = 
  if perception_type <> "Iris-setosa" && perception_type <> "Iris-versicolor" && perception_type <> "Iris-virginica" then
    "ERROR: Invalid type of Iris"
  else
    perception_type + ":\n\n" + "Vector w = [" + w_vector.ToString() + "]" + " computed
      with an increment rate of " + incr_rate.ToString() + 
      (if use_bound then " with a missclassification upper bound of " + bound.ToString() else " with no missclassification upper bound")
      + " for each element."

System.IO.File.WriteAllLines(path, [output_text])
Console.WriteLine("Done!")


      
